package com.pig4cloud.pigx.contract.executer.order.supporter;

import com.pig4cloud.pigx.common.customize.contants.SystemConfigConstants;
import com.pig4cloud.pigx.common.customize.utils.BigDecimalUtils;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import lombok.AllArgsConstructor;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Objects;

/**
 * 订单相关公式计算
 *
 * @since 2022/8/17 18:19
 */
@AllArgsConstructor
public class OrderCalculator {

    /**
     * 计算开仓损失
     * <p>下单方向：1为多单；-1为空单
     * <p>合约数量 * 绝对值 {min[0, 订单方向 * (标记价格 - 订单价格)}
     *
     * @param side      方向
     * @param num       数量
     * @param price     价格
     * @param markPrice 标记价
     * @return {@link BigDecimal}
     * @since 2022/9/8 19:07
     */
    public static BigDecimal getOpeningLoss(String side, BigDecimal num, BigDecimal price, BigDecimal markPrice) {
        // 根据订单方向获取计算开仓损失时的值
        BigDecimal sideValue = getSideValue(side);
        // 求出差额
        BigDecimal difference = sideValue.multiply(markPrice.subtract(price));
        // 比较并获得结果的绝对值
        BigDecimal abs = difference.compareTo(BigDecimal.ZERO) < 0 ? difference.abs() : BigDecimal.ZERO;

        // 计算开仓损失
        return num.multiply(abs);
    }

    /**
     * 根据订单方向获取计算开仓损失时的值
     *
     * @param side 方向（0做多、1做空）
     * @return {@link BigDecimal}
     * @since 2022/9/8 19:28
     */
    public static BigDecimal getSideValue(String side) {
        if (Objects.equals(ContractOrderEnum.Side.MORE.getValue(), side)) {
            return BigDecimal.ONE;
        } else if (Objects.equals(ContractOrderEnum.Side.EMPTY.getValue(), side)) {
            return BigDecimal.valueOf(-1);
        } else {
            throw new RuntimeException("数据异常，未能识别的订单方向");
        }
    }

    /**
     * 做多市价单计算假设价格
     * <p>买一价 * (1 + 0.05%)
     *
     * @param buyOrderHighestPrice 买单最高价
     * @return {@link BigDecimal}
     * @since 2022/9/8 19:13
     */
    public static BigDecimal getAssumingPriceLong(BigDecimal buyOrderHighestPrice) {
        return buyOrderHighestPrice.multiply(BigDecimal.valueOf(1.00005));
    }

    /**
     * 做空市价单获得假设价格
     * <p>卖一价、标记价取最高
     *
     * @param saleOrderHighestPrice 卖单最低价
     * @return {@link BigDecimal}
     * @since 2022/9/8 19:13
     */
    public static BigDecimal getAssumingPriceLong(BigDecimal saleOrderHighestPrice, BigDecimal markPrice) {
        return saleOrderHighestPrice.compareTo(markPrice) > 0 ?
                saleOrderHighestPrice
                : markPrice;
    }

    /**
     * 订单面值
     * <p>公式：订单面值 = 数量 * 价格
     *
     * @param price 价格
     * @param num   数量
     * @return {@link BigDecimal}
     * @since 2022/8/17 18:20
     */
    public static BigDecimal getOrderValue(@NotNull BigDecimal price,
                                           @NotNull BigDecimal num) {

        return CommonUtil.setUSDTScale(price.multiply(num));
    }

    /**
     * 手续费
     * <p>公式：手续费 = 订单面值 * 手续费率
     *
     * @param orderValue      订单面值
     * @param handlingFeeRate 手续费率
     * @return {@link BigDecimal}
     * @since 2022/8/17 18:20
     */
    public static BigDecimal getHandlingFee(@NotNull BigDecimal orderValue,
                                            @NotNull BigDecimal handlingFeeRate) {
        BigDecimal percent = BigDecimalUtils.toPercent(handlingFeeRate);

        return CommonUtil.setUSDTScale(orderValue.multiply(percent));
    }

    /**
     * 保证金
     * <p>公式：保证金 = 订单面值 / 杠杆倍数
     *
     * @param orderValue 订单面值
     * @param multiple   杠杆倍数
     * @return {@link BigDecimal}
     * @since 2022/8/17 18:20
     */
    public static BigDecimal getMargin(@NotNull BigDecimal orderValue,
                                       @NotNull Integer multiple) {
        return orderValue.divide(BigDecimal.valueOf(multiple), SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode);
    }

    /**
     * 收益
     * <p>公式：
     * <p>做多收益 = (成交价格 - 开仓均价) * 成交数量
     * <p>做空收益 = (开仓均价 - 成交价格) * 成交数量
     *
     * @param price        成交价格
     * @param openAvgPrice 开仓均价
     * @param num          成交数量
     * @param side         订单方向（0做多、1做空）
     * @return {@link BigDecimal}
     * @since 2022/8/19 18:18
     */
    public static BigDecimal getProfit(@NotNull BigDecimal price,
                                       @NotNull BigDecimal openAvgPrice,
                                       @NotNull BigDecimal num,
                                       @NotBlank String side) {
        BigDecimal profit;

        // 做多
        if (Objects.equals(ContractOrderEnum.Side.MORE.getValue(), side)) {
            profit = price.subtract(openAvgPrice).multiply(num);
        } else {
            profit = openAvgPrice.subtract(price).multiply(num);
        }

        return CommonUtil.setUSDTScale(profit);
    }


    /**
     * 计算收益率
     * <p>做多：（平仓均价 - 开仓均价）/ 开仓均价 * 杠杆倍数
     * <p>做空：（开仓均价 - 平仓均价）/ 开仓均价 * 杠杆倍数
     *
     * @param openAvgPrice  开仓均价
     * @param closeAvgPrice 平仓均价
     * @param multiple      杠杆倍数
     * @param side          方向
     * @return {@link BigDecimal}
     * @since 2022/9/4 15:08
     */
    public static BigDecimal getProfitRate(@NotNull BigDecimal openAvgPrice,
                                           @NotNull BigDecimal closeAvgPrice,
                                           @NotNull Integer multiple,
                                           @NotBlank String side) {

        // 做多
        if (Objects.equals(ContractOrderEnum.Side.MORE.getValue(), side)) {
            return closeAvgPrice
                    .subtract(openAvgPrice)
                    .divide(openAvgPrice, SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode)
                    .multiply(BigDecimal.valueOf(multiple));
        } else {
            return openAvgPrice
                    .subtract(closeAvgPrice)
                    .divide(openAvgPrice, SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode)
                    .multiply(BigDecimal.valueOf(multiple));
        }
    }

    /**
     * 强平费
     * <p>公式：强平费 = 订单面值 * 强平费率
     *
     * @param orderValue       订单面值
     * @param liquidateFeeRate 强平费率
     * @return {@link BigDecimal}
     * @since 2022/8/17 18:20
     */
    public static BigDecimal getLiquidateFee(@NotNull BigDecimal orderValue,
                                             @NotNull BigDecimal liquidateFeeRate) {
        BigDecimal percent = BigDecimalUtils.toPercent(liquidateFeeRate);

        return orderValue.multiply(percent);
    }

    /**
     * 根据限价单上限比率、价格获得上限价格
     *
     * @param limitOrderPriceCapRatio 限价单上限比率
     * @param price                   价格
     * @return {@link BigDecimal}
     * @since 2022/9/2 21:33
     */
    public static BigDecimal getLimitPriceCap(BigDecimal limitOrderPriceCapRatio, BigDecimal price) {
        if (Objects.isNull(limitOrderPriceCapRatio)) {
            throw new RuntimeException("未能获得币对信息");
        }

        return BigDecimal.ONE
                .add(BigDecimalUtils.toPercent(limitOrderPriceCapRatio))
                .multiply(price);
    }

    /**
     * 根据限价单下限比率、价格获得下限价格
     *
     * @param limitOrderPriceFloorRatio 限价单下限比率
     * @param price                     价格
     * @return {@link BigDecimal}
     * @since 2022/9/2 21:33
     */
    public static BigDecimal getLimitPriceFloor(BigDecimal limitOrderPriceFloorRatio, BigDecimal price) {
        if (Objects.isNull(limitOrderPriceFloorRatio)) {
            throw new RuntimeException("未能获得币对信息");
        }

        return BigDecimal.ONE
                .subtract(BigDecimalUtils.toPercent(limitOrderPriceFloorRatio))
                .multiply(price);
    }

    /**
     * 计算平均价格
     *
     * @param oldAvgPrice 旧平均价格
     * @param oldNum      旧数量
     * @param price       价格
     * @param num         数量
     * @return {@link BigDecimal}
     * @since 2022/9/2 21:33
     */
    public static BigDecimal getAvgPrice(BigDecimal oldAvgPrice,
                                         BigDecimal oldNum,
                                         BigDecimal price,
                                         BigDecimal num) {
        // 旧平均价格、旧数量为空，赋默认值
        if (Objects.isNull(oldAvgPrice)) {
            oldAvgPrice = BigDecimal.ZERO;
        }
        if (Objects.isNull(oldNum)) {
            oldAvgPrice = BigDecimal.ZERO;
        }

        BigDecimal oldValue = oldAvgPrice.multiply(oldNum);
        BigDecimal newValue = price.multiply(num);
        BigDecimal totalValue = oldValue.add(newValue);
        BigDecimal totalNum = oldNum.add(num);

        if (totalNum.compareTo(BigDecimal.ZERO) <= 0) {
            throw new RuntimeException("数据异常，数量小于等于0");
        }

        // 根据总价值除以总数量，得出平均价格
        return totalValue.divide(totalNum, SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode);
    }
}
